package cn.coisini.model.common.log.aspect;

import cn.coisini.model.common.log.annotation.Log;
import cn.coisini.model.common.log.service.AsyncOperLogService;
import cn.coisini.model.pojo.AsyncOperLog;
import cn.coisini.utils.IdWorker;
import cn.coisini.utils.IpUtils;
import cn.coisini.utils.JwtUtil;
import com.alibaba.fastjson2.JSON;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import java.util.Collection;
import java.util.Map;

/**
 * @author xiangshaw
 * Description: 操作日志记录处理
 */
// 使用aop操作
@Aspect
@Component
@RequiredArgsConstructor
public class LogAspect {

    private static final Logger log = LoggerFactory.getLogger(LogAspect.class);

    private final AsyncOperLogService asyncOperLogService;

    /**
     * 在带有 @Log 注解的方法成功执行后记录日志。
     * （后置通知）
     * @param joinPoint     切点（方法）
     * @param controllerLog 方法上的 @Log 注解
     * @param jsonResult    方法返回的结果
     */
    @AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult")
    public void doAfterReturning(JoinPoint joinPoint, Log controllerLog, Object jsonResult) {
        handleLog(joinPoint, controllerLog, null, jsonResult);
    }

    /**
     * 在带有 @Log 注解的方法抛出异常后记录日志。
     *
     * @param joinPoint     切点（方法）
     * @param controllerLog 方法上的 @Log 注解
     * @param e             方法抛出的异常
     */
    @AfterThrowing(value = "@annotation(controllerLog)", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Log controllerLog, Exception e) {
        handleLog(joinPoint, controllerLog, e, null);
    }

    /**
     * 核心日志处理方法，处理成功和异常操作日志。
     *
     * @param joinPoint     切点（方法）
     * @param controllerLog 方法上的 @Log 注解
     * @param e             方法抛出的异常（如果有则记录异常日志）
     * @param jsonResult    方法返回的结果（如果无异常则记录返回结果）
     */
    private void handleLog(JoinPoint joinPoint, Log controllerLog, Exception e, Object jsonResult) {
        try {
            // 获取当前HTTP请求
            HttpServletRequest request = getCurrentHttpRequest();
            if (request == null) {
                log.error("无法获取当前HTTP请求");
                return;
            }
            // 创建并填充操作日志
            AsyncOperLog operLog = new AsyncOperLog();
            operLog.setId(generateUniqueId());
            // 操作日志数据保存数据库
            populateLogDetails(joinPoint, controllerLog, operLog, jsonResult, request, e);
            // 异步保存操作日志
            asyncOperLogService.saveSysLog(operLog);
        } catch (Exception exp) {
            // 记录日志处理过程中的异常
            log.error("==前置通知异常==");
            log.error("日志处理异常: {}", exp.getMessage(), exp);
        }
    }

    /**
     * 获取当前的HttpServletRequest。
     *
     * @return 当前的HttpServletRequest或null（如果无法获取）
     */
    private HttpServletRequest getCurrentHttpRequest() {
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        if (requestAttributes instanceof ServletRequestAttributes servletRequestAttributes) {
            return servletRequestAttributes.getRequest();
        }
        return null;
    }

    /**
     * 生成唯一标识符。
     *
     * @return 唯一标识符字符串
     */
    private String generateUniqueId() {
        return String.valueOf(new IdWorker().nextId());
    }

    /**
     * 填充日志详细信息。
     *
     * @param joinPoint     切点（方法）
     * @param controllerLog 方法上的 @Log 注解
     * @param operLog       操作日志对象
     * @param jsonResult    方法返回的结果
     * @param request       当前HTTP请求
     * @param e             方法抛出的异常
     */
    private void populateLogDetails(JoinPoint joinPoint, Log controllerLog, AsyncOperLog operLog,
                                    Object jsonResult, HttpServletRequest request, Exception e) {
        // 从Token中获取用户名和用户ID
        String userName = JwtUtil.getUsernameFromToken();
        String userId = JwtUtil.getUserIdFromToken();
        // 获取类名（通过joinPoint参数得到类的名字）
        String className = joinPoint.getTarget().getClass().getName();
        // 获取方法名
        String methodName = joinPoint.getSignature().getName();
        // 获取请求IP
        String ip = IpUtils.getIp(request);
        // 设置操作日志的相关属性
        operLog.setOperUserId(userId);
        operLog.setOperName(userName);
        // 设置Method值 例如：cn.coisini.navigation.controller.UserController.status()
        operLog.setMethod(String.format("%s.%s()", className, methodName));
        // 设置RequestMethod请求方式 例如：GET
        operLog.setRequestMethod(request.getMethod());
        // 当前请求路径
        operLog.setOperUrl(request.getRequestURI());
        // 设置请求的IP
        operLog.setOperIp(ip);
        // IP归属地
        operLog.setOperIpSource(IpUtils.getIp2region(ip));
        // IP 省市区
        operLog.setOperIpCity(IpUtils.getCityInfo(ip));
        // 如果有异常，状态设置为true 0
        operLog.setStatus(e != null);
        operLog.setErrorMsg(e != null ? e.getMessage() : null);
        // 设置注解中的信息
        setLogAnnotations(joinPoint, controllerLog, operLog, jsonResult);
    }

    /**
     * 获取注解中对方法的描述信息 用于Controller层注解
     *
     * @param joinPoint     切点（方法）
     * @param controllerLog 方法上的 @Log 注解
     * @param operLog       操作日志对象
     * @param jsonResult    方法返回的结果
     */
    private void setLogAnnotations(JoinPoint joinPoint, Log controllerLog, AsyncOperLog operLog, Object jsonResult) {
        // 设置标题 例如：用户管理
        operLog.setTitle(controllerLog.title());
        // 设置action动作 例如：STATUS
        operLog.setBusinessType(controllerLog.businessType().name());
        // 设置操作人类别 例如：MANAGE
        operLog.setOperatorType(controllerLog.operatorType().name());
        // 是否需要保存请求参数、参数、值
        if (controllerLog.saveRequestData()) {
            // 获取参数的信息，传入到数据库中。 异常的参数
            operLog.setOperParam(extractRequestParameters(joinPoint));
        }
        // 是否需要保存响应数据，
        if (controllerLog.saveResponseData() && jsonResult != null) {
            operLog.setJsonResult(JSON.toJSONString(jsonResult));
        }
    }

    /**
     * 提取请求参数并转换为字符串。
     *
     * @param joinPoint 切点（方法）
     * @return 请求参数的字符串表示
     */
    private String extractRequestParameters(JoinPoint joinPoint) {
        StringBuilder params = new StringBuilder();
        for (Object arg : joinPoint.getArgs()) {
            if (arg != null && !shouldFilterObject(arg)) {
                params.append(JSON.toJSONString(arg)).append(" ");
            }
        }
        return params.toString().trim();
    }

    /**
     * 判断对象是否需要被过滤出日志。
     *
     * @param obj 要检查的对象
     * @return 如果对象是需要被过滤返回true；否则返回false
     */
    private boolean shouldFilterObject(Object obj) {
        // 检查是否为MultipartFile或HTTP相关对象
        if (obj instanceof MultipartFile || obj instanceof HttpServletRequest || obj instanceof HttpServletResponse || obj instanceof BindingResult) {
            return true;
        }
        // 检查是否为数组
        if (obj.getClass().isArray()) {
            return MultipartFile.class.isAssignableFrom(obj.getClass().getComponentType());
        }
        // 检查是否为集合
        if (obj instanceof Collection) {
            for (Object item : (Collection<?>) obj) {
                if (item instanceof MultipartFile) {
                    return true;
                }
            }
        }
        // 检查是否为映射
        if (obj instanceof Map) {
            for (Object value : ((Map<?, ?>) obj).values()) {
                if (value instanceof MultipartFile) {
                    return true;
                }
            }
        }
        return false;
    }
}